package executables;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import data_structs.HashTable;
import data_structs.SequenceWrapper;
import errors.DuplicateKey;
import errors.IncorrectNucleotide;

public class SequenceMatcher {
	
	private static BufferedReader hashBuffer;
	private static BufferedReader searchBuffer;
	private static PrintWriter printer;
	
	private static HashTable<SequenceWrapper> ht;
	private static int kmer;
	private static int numInHashFile;
	private static int numInQueryFile;
	private static int totalHashed;
	private static int totalFound;

	public static void main(String[] args) throws IOException, IncorrectNucleotide, JSONException {
		//Windows file locations
		String f_51hits = "C:/Users/DC/Documents/Work/Miscellaneous/51.hits.fa";
		String f1 = "C:/Users/DC/Documents/Work/workspace/SequenceMatching/src/files/testfile1.txt";
		String f2 = "C:/Users/DC/Documents/Work/workspace/SequenceMatching/src/files/testfile2.txt";
		String f3 = "C:/Users/DC/Documents/Work/workspace/SequenceMatching/src/files/testfile3.txt";
		String microbe377Small = "C:/Users/DC/Documents/Work/workspace/SequenceMatching/src/files/m377-small.txt";
		String microbe114Small = "C:/Users/DC/Documents/Work/workspace/SequenceMatching/src/files/m114-small.txt";
		String microbe377Full = "C:/Users/DC/Documents/Work/Metagenomes/microbe377/55.fna";
		String microbe114Full = "C:/Users/DC/Documents/Work/Metagenomes/microbe114/57.fna";
		
		//Mac file locations
//		String f_51hits = "/Users/DG/Documents/Daniel Work/Miscellaneous/51.hits.fa";
//		String f1 = "/Users/DG/Documents/Daniel Work/SequenceMatching/src/files/testfile1.txt";
//		String f2 = "/Users/DG/Documents/Daniel Work/workspace/SequenceMatching/src/files/testfile2.txt";
//		String f3 = "/Users/DG/Documents/Daniel Work/workspace/SequenceMatching/src/files/testfile3.txt";
//		String microbe377Small = "/Users/DG/Documents/Daniel Work/workspace/SequenceMatching/src/files/m377-small.txt";
//		String microbe114Small = "/Users/DG/Documents/Daniel Work/workspace/SequenceMatching/src/files/m114-small.txt";
//		String microbe377Full = "/Users/DG/Documents/Daniel Work/Metagenomes/microbe377/55.fna";
//		String microbe114Full = "/Users/DG/Documents/Daniel Work/Metagenomes/microbe114/57.fna";
		
		//octopussy file locations
//		String f_51hits = "/home/dcuevas/sequencematching/src/files/51.hits.fa";
//		String microbe377Small = "/home/dcuevas/sequencematching/src/files/m377-small.txt";
//		String microbe114Small = "/home/dcuevas/sequencematching/src/files/m114-small.txt";
		
		File hashFile = new File(f_51hits);
		File queryFile = new File(microbe377Small);
		kmer = 15;
		totalHashed = 0;
		
		Calendar cal = Calendar.getInstance();
		SimpleDateFormat sdf = new SimpleDateFormat("HH.mm.ss");
		String time = sdf.format(cal.getTime());
//		fname = "results/"+resultsFileName+".txt";
		
		//User inputed filename
		BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
		//If user input is blank, loop around
		String inputname, fname;
		for(;;) {
			System.out.print("Enter name of file:");
			inputname = (input.readLine()).trim();
			
			if( inputname.length() != 0 ) {
				fname = "results/"+inputname+"@"+time+".txt";
				break;
			}
		}
		
//		printer = new PrintWriter(new FileWriter(fname));
//		printer.println("Hashed File: "+hashFile.getName());
//		printer.println("Query File: "+queryFile.getName());
//		printer.println();
		
		//Timing variables
		long startHash, startSearch;
		double hTime, sTime;
		
		/**
		 * Hash Section
		 */
		//Read in hash file
		hashBuffer = new BufferedReader(new FileReader(hashFile));
		
		ht = new HashTable<SequenceWrapper>(2778779);
//		ht = new HashTable<SequenceWrapper>();
		startHash = System.currentTimeMillis();
		numInHashFile = 0;
		
		//Read-hash-file loop
		while( hashBuffer.ready() ) {
			if( createHashTable() == null ) return;
			numInHashFile++;
			
			//Limit hash table size to control heap size
			/*while( ht.numObjects() < 2500000 && hashBuffer.ready() ) //2.5 million limit
				if( createHashTable() == null ) return;*/
		}
		hTime = (double) (System.currentTimeMillis()-startHash)/1000;
		hashBuffer.close();
		
		/**
		 * Search Section
		 */
		System.out.println("Starting search...");
		startSearch = System.currentTimeMillis();
		
		//Read in query file
		searchBuffer = new BufferedReader(new FileReader(queryFile));
		
		//Read-query-file loop
		numInQueryFile = totalFound = 0;
		while( searchBuffer.ready() ) {
			if( !searchForSeq() ) throw new UnsupportedOperationException("Failed search");
			
			numInQueryFile++;
			if( numInQueryFile % 1000 == 0 ) {
				System.out.println("Searched for "+numInQueryFile+" sequences.");
//				printer.close();
//				
//				cal = Calendar.getInstance();
//				sdf = new SimpleDateFormat("HH.mm.ss");
//				time = sdf.format(cal.getTime());
//				fname = "results/"+inputname+"/"+time+".txt";
//				printer = new PrintWriter(new FileWriter(fname));
			}
		}
		
		sTime = (double) (System.currentTimeMillis()-startSearch)/1000;
		double totalTime = (double) hTime+sTime;
		searchBuffer.close();
		
		double similarity = (double) totalFound/numInQueryFile;
//		printer.close();
		printer = new PrintWriter(new FileWriter("results/"+inputname+"-results.txt"));
		printer.println("***Time Results***");
		printer.println("Hash time: "+hTime+" seconds");
		printer.println("Search time: "+sTime+" seconds");
		printer.println("Total time: "+totalTime+" seconds");
		printer.println("Number of objects hashed: "+totalHashed);
		printer.println("Number of "+kmer+"-tuples found: "+totalFound);
		printer.close();
		
		
		System.out.println("***Time Results***");
		System.out.println("Hash time: "+hTime+" seconds");
		System.out.println("Search time: "+sTime+" seconds");
		System.out.println("Total time: "+totalTime+" seconds");
		System.out.println("Number of objects hashed: "+totalHashed);
		System.out.println("Number of "+kmer+"-tuples found: "+totalFound);
//		System.out.println("Memory left over: "+java.lang.Runtime.getRuntime().freeMemory());
	}//End main
	
	/*****************
	 * Utility methods
	 *****************/
	
	private static HashTable<SequenceWrapper> createHashTable() throws IOException {
		String txt = hashBuffer.readLine();
		String id = txt.substring(1);
		String sequence = hashBuffer.readLine();
		
		System.out.println("Hashing id#"+id);
		
		int seqIdx = 0; //Use for position of sequence
		int endLoop = sequence.length()-kmer; //Stopping condition
		while( seqIdx <= endLoop ) {
			try {
				String seq = sequence.substring(seqIdx, seqIdx+kmer);
				String compSeq = reversal(getComplement(seq)); //Complement sequence
				
				//Objects to add to hash table
				SequenceWrapper sw = new SequenceWrapper(id,seq,seqIdx, false);
				SequenceWrapper compSW = new SequenceWrapper(id,compSeq,seqIdx, true);
				//Loop to keep trying to add sw when hash returns false
				while( !ht.add(sw) )
					sw.increaseTreeFixer();
				seqIdx++;
				//Loop to keep trying to add swComp when hash returns false
				while( !ht.add(compSW) )
					compSW.increaseTreeFixer();
				totalHashed = totalHashed+2;
			} catch(IncorrectNucleotide e) {
				e.printStackTrace();
				return null;
			}
		} //End while loop through sequence
		return ht;
	}//End createHashTable
	
	private static String reversal(String seq) {
		return seq = new StringBuffer(seq).reverse().toString();
	}
	
	private static String getComplement(String seq) throws IncorrectNucleotide {
		String ret = "";
		for( int i = 0; i < seq.length(); i++ ) {
			char curr = seq.charAt(i);
			switch(curr) {
				case 'A': ret += "T"; break;
				case 'T': ret += "A"; break;
				case 'C': ret += "G"; break;
				case 'G': ret += "C"; break;
				case 'N': ret += "N"; break;
				default: throw new IncorrectNucleotide("Nucleotide '" + curr + "' is not valid.");
			}
		}
		return ret;
	}//End getComplement
	
	private static boolean searchForSeq() throws IOException, JSONException {
		String txt = searchBuffer.readLine();
		String id = txt.substring(1);
		String sequence = searchBuffer.readLine();
		
		int seqIdx = 0; //Use for position of sequence
		int endLoop = sequence.length()-kmer; //Stopping condition
		while( seqIdx <= endLoop ) {
			try {
				String seqToFind = sequence.substring(seqIdx, seqIdx+kmer);
				
				SequenceWrapper sw = new SequenceWrapper(id,seqToFind,seqIdx,false);
				ArrayList<SequenceWrapper> results = ht.contains(sw);
				if( results != null && results.size() != 0 ) {
					totalFound += results.size();
					//JSON object used to format printing
					JSONObject json = new JSONObject("{seq:"+seqToFind+",id:"+id+",position:"+seqIdx+",matches:"+new JSONArray()+"}");
					
					for( SequenceWrapper obj : results ) {
						String val = "{id:"+obj.getID()+",position:"+obj.getPosition()+",complement:"+obj.isComplement()+"}";
						json.accumulate("matches", val);
					}
					//Print entire json to file
//					printer.println(json);
					int numFound = results.size();
//					printer.println((numFound == 1) ? "Found 1 match." : "Found "+numFound+" matches.");
				}
				seqIdx++;
			} catch (IncorrectNucleotide e) {
				e.printStackTrace();
				return false;
			}
		} //End while loop through sequence
//		System.out.println("Finished sequence id "+id);
		return true;
	}//End searchForSeq
	
}//End class
